/*
 * Copyright 2018 Rami Jemli
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.ramijemli.percentagechartview.renderer;

import com.ramijemli.percentagechartview.IPercentageChartView;
import com.ramijemli.percentagechartview.callback.AdaptiveColorProvider;
import ohos.agp.components.AttrSet;
import ohos.agp.render.Arc;
import ohos.agp.render.Canvas;
import ohos.agp.render.LinearShader;
import ohos.agp.render.Paint;
import ohos.agp.render.RadialShader;
import ohos.agp.render.Shader;
import ohos.agp.render.SweepShader;
import ohos.agp.utils.Color;
import ohos.agp.utils.Matrix;
import ohos.agp.utils.Point;
import ohos.agp.utils.RectFloat;


/**
 * Pie mode renderer
 */
public class PieModeRenderer extends BaseModeRenderer implements OrientationBasedMode, OffsetEnabledMode {
    /**
     * The constant M bg start angle
     */
    private float mBgStartAngle;
    /**
     * The constant M bg sweep angle
     */
    private float mBgSweepAngle;

    /**
     * Pie mode renderer
     *
     * @param view view
     */
    public PieModeRenderer(IPercentageChartView view) {
        super(view);
        setup();
    }

    /**
     * Pie mode renderer
     *
     * @param view  view
     * @param attrs attrs
     */
    public PieModeRenderer(IPercentageChartView view, AttrSet attrs) {
        super(view, attrs);
        setup();
    }

    /**
     * Setup
     */
    @Override
    void setup() {
        super.setup();
        updateDrawingAngles();
    }

    /**
     * Measure *
     *
     * @param width         w
     * @param height        h
     * @param paddingLeft   padding left
     * @param paddingTop    padding top
     * @param paddingRight  padding right
     * @param paddingBottom padding bottom
     */
    @Override
    public void measure(int width, int height, int paddingLeft, int paddingTop, int paddingRight, int paddingBottom) {
        float centerX = width * 0.5f;
        float centerY = height * 0.5f;
        float radius = Math.min(width, height) * 0.5f;

        mCircleBounds = new RectFloat(centerX - radius,
                centerY - radius,
                centerX + radius,
                centerY + radius);
        measureBackgroundBounds();
        setupGradientColors(mCircleBounds);
        updateText();
    }

    /**
     * Measure background bounds
     */
    private void measureBackgroundBounds() {
        mBackgroundBounds = new RectFloat(mCircleBounds.left + mBackgroundOffset,
                mCircleBounds.top + mBackgroundOffset,
                mCircleBounds.right - mBackgroundOffset,
                mCircleBounds.bottom - mBackgroundOffset);
    }

    /**
     * Draw *
     *
     * @param canvas canvas
     */
    @Override
    public void draw(Canvas canvas) {
        if (mGradientType == GRADIENT_SWEEP && mView.isInEditMode()) {
            // TO GET THE RIGHT DRAWING START ANGLE FOR SWEEP GRADIENT'S COLORS IN PREVIEW MODE
            canvas.save();
            canvas.rotate(mStartAngle, mCircleBounds.getCenter().getPointX(), mCircleBounds.getCenter().getPointY());
        }

        // FOREGROUND
        canvas.drawArc(mCircleBounds, new Arc(mStartAngle, mSweepAngle, true), mProgressPaint);

        // BACKGROUND
        if (mDrawBackground) {
            canvas.drawArc(mBackgroundBounds, new Arc(mBgStartAngle, mBgSweepAngle, true), mBackgroundPaint);
        }

        if (mGradientType == GRADIENT_SWEEP && mView.isInEditMode()) {
            // TO GET THE RIGHT DRAWING START ANGLE FOR SWEEP GRADIENT'S COLORS IN PREVIEW MODE
            canvas.restore();
        }

        // TEXT
        drawText(canvas);
    }

    /**
     * Set adaptive color provider *
     *
     * @param adaptiveColorProvider adaptive color provider
     */
    @Override
    public void setAdaptiveColorProvider(AdaptiveColorProvider adaptiveColorProvider) {
        if (adaptiveColorProvider == null) {
            mProgressColorAnimator = mBackgroundColorAnimator = mTextColorAnimator = null;
            mAdaptiveColorProvider = null;
            mTextPaint.setColor(new Color(mTextColor));
            mBackgroundPaint.setColor(new Color(mBackgroundColor));
            mProgressPaint.setColor(new Color(mBackgroundColor));
            mView.postInvalidate();
            return;
        }

        this.mAdaptiveColorProvider = adaptiveColorProvider;

        setupColorAnimations();
        updateProvidedColors(mProgress);
        mView.postInvalidate();
    }

    /**
     * Setup gradient colors *
     *
     * @param bounds bounds
     */
    @Override
    void setupGradientColors(RectFloat bounds) {
        if (mGradientType == -1 && bounds.getHeight() == 0) {
            return;
        }

        switch (mGradientType) {
            default:
            case GRADIENT_LINEAR:
                Point[] points = new Point[]{new Point(bounds.getCenter().getPointX(), bounds.top),
                        new Point(bounds.getCenter().getPointX(), bounds.bottom)};
                mGradientShader = new LinearShader(points, mGradientDistributions, mGradientColors,
                        Shader.TileMode.CLAMP_TILEMODE);
                updateGradientAngle(mGradientAngle);
                mProgressPaint.setShader(mGradientShader, Paint.ShaderType.LINEAR_SHADER);
                break;

            case GRADIENT_RADIAL:
                mGradientShader = new RadialShader(new Point(bounds.getCenter().getPointX(),
                        bounds.getCenter().getPointY()),
                        bounds.bottom - bounds.getCenter().getPointY(), mGradientDistributions,
                        mGradientColors, Shader.TileMode.MIRROR_TILEMODE);
                mProgressPaint.setShader(mGradientShader, Paint.ShaderType.RADIAL_SHADER);
                break;

            case GRADIENT_SWEEP:
                mGradientShader = new SweepShader(bounds.getCenter().getPointX(), bounds.getCenter().getPointY(),
                        mGradientColors, mGradientDistributions);
                if (!mView.isInEditMode()) {
                    // THIS BREAKS SWEEP GRADIENT'S PREVIEW MODE
                    updateGradientAngle(mStartAngle);
                }
                mProgressPaint.setShader(mGradientShader, Paint.ShaderType.SWEEP_SHADER);
                break;
        }
    }

    /**
     * Update drawing angles
     */
    @Override
    void updateDrawingAngles() {
        switch (orientation) {
            case ORIENTATION_COUNTERCLOCKWISE:
                mSweepAngle = -(mProgress / DEFAULT_MAX * 360);
                mBgStartAngle = mStartAngle;
                mBgSweepAngle = 360 + mSweepAngle;
                break;

            default:
            case ORIENTATION_CLOCKWISE:
                mSweepAngle = mProgress / DEFAULT_MAX * 360;
                mBgStartAngle = mStartAngle + mSweepAngle;
                mBgSweepAngle = 360 - mSweepAngle;
                break;
        }
    }

    /**
     * Update gradient angle *
     *
     * @param angle angle
     */
    @Override
    void updateGradientAngle(float angle) {
        if (mGradientType == -1 || mGradientType == GRADIENT_RADIAL) {
            return;
        }
        Matrix matrix = new Matrix();
        matrix.postRotate(angle, mCircleBounds.getCenter().getPointX(), mCircleBounds.getCenter().getPointY());
        mGradientShader.setShaderMatrix(matrix);
    }

    /**
     * Get orientation int
     *
     * @return the int
     */
    public int getOrientation() {
        return orientation;
    }

    /**
     * Set orientation *
     *
     * @param orientation orientation
     */
    public void setOrientation(int orientation) {
        if (this.orientation == orientation) {
            return;
        }
        this.orientation = orientation;
        updateDrawingAngles();
    }

    /**
     * Set start angle *
     *
     * @param startAngle start angle
     */
    @Override
    public void setStartAngle(float startAngle) {
        if (this.mStartAngle == startAngle) {
            return;
        }
        this.mStartAngle = startAngle;
        updateDrawingAngles();
        if (mGradientType == GRADIENT_SWEEP) {
            updateGradientAngle(startAngle);
        }
    }

    /**
     * Get background offset int
     *
     * @return the int
     */
    public int getBackgroundOffset() {
        return mBackgroundOffset;
    }

    /**
     * Set background offset *
     *
     * @param backgroundOffset background offset
     */
    public void setBackgroundOffset(int backgroundOffset) {
        if (!mDrawBackground || this.mBackgroundOffset == backgroundOffset) {
            return;
        }
        this.mBackgroundOffset = backgroundOffset;
        measureBackgroundBounds();
    }

}
